#ifndef ESMD_CONF_H_
#define ESMD_CONF_H_
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "esmd_types.h"

#define CONF_FILE_FMT                                                                                   \
  CONF_STRING(root, "$WORKDIR");                                                                              \
  CONF_STRING(structure, NULL);                                                                         \
  CONF_STRING(coordinates, NULL);                                                                       \
  CONF_STRING(parameters, NULL);                                                                        \
  CONF_REAL(rcut, 12);                                                                                  \
  CONF_REAL(skin, 2);                                                                                   \
  CONF_REAL(rswitch, 10);                                                                               \
  CONF_REAL(coulscaling, 1);                                                                            \
  CONF_REAL(coulconst, 332);                                                                            \
  CONF_REALBOX(cell, 0, 0, 0, 0, 0, 0);                                                                 \
  CONF_INTVEC(msmlevels, 0, 0, 0);                                                                      \
  CONF_INT(msmorder, 4);                                                                                \
  CONF_REAL(dt, 1);                                                                                     \
  CONF_INT(nsteps, 0);                                                                                  \
  CONF_INT(trajfreq, 10);                                                                               \
  CONF_STRING(trajectory, NULL);                                                                         \
  CONF_REAL(tempstart, 0);                                                                              \
  CONF_REAL(tempstop, 0);                                                                               \
  CONF_REAL(tempdamp, 0);                                                                               \
  CONF_ENUM(tempcouple, TC_NONE, E(TC, NONE), E(TC, NH));                                               \
  CONF_REAL(tempdrag, 0);                                                                               \
  CONF_INT(tempchain, 3);                                                                               \
  CONF_INT(tempnc, 1);                                                                                  \
  CONF_STRING(rigids, "");                                                                              \
  CONF_ENUM(rigidtype, RIGID_NONE, E(RIGID, NONE), E(RIGID, SHAKE), E(RIGID, RATTLE), E(RIGID, LINCS)); \
  CONF_ENUM(coultype, COUL_RF, E(COUL, RF), E(COUL, MSM), E(COUL, SHIFTED));                            \
  CONF_REAL(temperature, 310);                                                                          \
  CONF_INT(minimize, 0);                                                                                \
  CONF_INT(replicate, 0);                                                                               \
  CONF_REAL(maxeval, 1000000);                                                                          \
  CONF_REAL(etol, 1e-4);                                                                                \
  CONF_REAL(ftol, 1e-4);                                                                                \
  CONF_REAL(rigidscale, 64);                                                                            \
  CONF_ENUM(units, UNITS_REAL, E(UNITS, REAL));                                                         \
  /*These variables are set regarding to units*/                                                        \
  CONF_REAL(ftm2v, 0);                                                                                  \
  CONF_REAL(mvv2e, 0);                                                                                  \
  CONF_REAL(boltz, 0);                                                                                  \
  CONF_INT(restfreq, 1);                                                                              \
  CONF_STRING(restart, NULL);

/* we firstly generate enums */
#define CONF_STRING(...)
#define CONF_REAL(...)
#define CONF_INT(...)
#define CONF_REALBOX(...)
#define CONF_INTVEC(...)

#define E(x, y) x##_##y
#define CONF_ENUM(name, default, ...) \
  enum name##_opts { __VA_ARGS__ }
CONF_FILE_FMT
#undef E
#undef CONF_ENUM

#define E(x, y) #y
#define CONF_ENUM(name, default, ...) static const char *name##_names[] = {__VA_ARGS__}
CONF_FILE_FMT
#undef E
#undef CONF_ENUM

#define E(x, y) \
  ({if (!strcasecmp(in, #y)) \
    return x ## _ ## y; })
#define CONF_ENUM(name, default, ...)       \
  __always_inline int parse_##name(const char *in) { \
    __VA_ARGS__;                            \
    puts("Unrecognized " #name);            \
    exit(1);                                \
  }
CONF_FILE_FMT
#undef E
#undef CONF_ENUM
/* finish generating enums*/

#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC

#define CONF_STRING(x, d) const char *x
#define CONF_REAL(x, d) double x
#define CONF_INT(x, d) int x
#define CONF_REALBOX(x, ...) box<double> x
#define CONF_INTVEC(x, ...) vec<int> x
#define CONF_ENUM(x, ...) int x
typedef struct esmd_config {
  CONF_FILE_FMT;
} esmd_config_t;
#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC
#undef CONF_ENUM

#define MAX_CONF_LINE 65536
#define EXPAND_BOX(k, xlo, ylo, zlo, xhi, yhi, zhi) \
  {                                                 \
    conf->k.lo.x = xlo;                             \
    conf->k.lo.y = ylo;                             \
    conf->k.lo.z = zlo;                             \
    conf->k.hi.x = xhi;                             \
    conf->k.hi.y = yhi;                             \
    conf->k.hi.z = zhi;                             \
  }
#define EXPAND_VEC(k, xv, yv, zv) \
  {                               \
    conf->k.x = xv;               \
    conf->k.y = yv;               \
    conf->k.z = zv;               \
  }
__always_inline void parse_esmd_config(esmd_config_t *conf, FILE *f) {
#define CONF_STRING(k, d) conf->k = d;
#define CONF_REAL(k, d) conf->k = d;
#define CONF_INT(k, d) conf->k = d;
#define CONF_REALBOX(k, ...) EXPAND_BOX(k, __VA_ARGS__)
#define CONF_INTVEC(k, ...) EXPAND_VEC(k, __VA_ARGS__)
#define CONF_ENUM(k, d, ...) conf->k = d;
  CONF_FILE_FMT;
#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC
#undef CONF_ENUM
  char buf[MAX_CONF_LINE];
  while (fgets(buf, MAX_CONF_LINE, f)) {
    char *p = buf;
    while (*p && isspace(*p))
      p++;
    char *key = p;
    if (!*key || *key == '#')
      continue;
    char *last = p;
    while (*p && *p != '=') {
      if (!isspace(*p))
        last = p;
      p++;
    }
    *(last + 1) = 0;
    if (*p == '=')
      p++;
    while (*p && isspace(*p))
      p++;
    char *value = p;
    char *vlast = p;
    while (*p) {
      if (!isspace(*p))
        vlast = p;
      p++;
    }
    *(vlast + 1) = 0;
#define CONF_STRING(k, d) \
  if (!strcmp(key, #k))   \
    conf->k = strdup(value);
#define CONF_REAL(k, d) \
  if (!strcmp(key, #k)) \
    sscanf(value, "%lf", &conf->k);
#define CONF_INT(k, d)  \
  if (!strcmp(key, #k)) \
    sscanf(value, "%d", &conf->k);
#define CONF_REALBOX(k, ...) \
  if (!strcmp(key, #k))      \
    sscanf(value, "%lf%lf%lf%lf%lf%lf", &conf->k.lo.x, &conf->k.lo.y, &conf->k.lo.z, &conf->k.hi.x, &conf->k.hi.y, &conf->k.hi.z);
#define CONF_INTVEC(k, ...) \
  if (!strcmp(key, #k))     \
    sscanf(value, "%d%d%d", &conf->k.x, &conf->k.y, &conf->k.z);
#define CONF_ENUM(k, ...) \
  if (!strcmp(key, #k))   \
    conf->k = parse_##k(value);
    CONF_FILE_FMT;
#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC
#undef CONF_ENUM
  }
  if (conf->units == UNITS_REAL) {
    conf->ftm2v = 0.00041839999997254776;
    conf->mvv2e = 2390.0573615334906;
    conf->boltz = 0.0019872066999999998;
  }
}

#include <mpi.h>
__always_inline void *get_conf_line_mpi(char *buf, FILE *f, int rank) {
  if (rank == 0) {
    char *res = fgets(buf, MAX_CONF_LINE, f);
    if (res == NULL)
      buf[0] = -1;
  }
  MPI_Bcast(buf, MAX_CONF_LINE, MPI_BYTE, 0, MPI_COMM_WORLD);
  if (buf[0] == -1)
    return NULL;
  else
    return buf;
}
__always_inline void parse_esmd_config_mpi(esmd_config_t *conf, const char *path, int rank) {
  FILE *f;
  if (rank == 0) {
    f = fopen(path, "r");
  }
#define CONF_STRING(k, d) conf->k = d;
#define CONF_REAL(k, d) conf->k = d;
#define CONF_INT(k, d) conf->k = d;
#define CONF_REALBOX(k, ...) EXPAND_BOX(k, __VA_ARGS__)
#define CONF_INTVEC(k, ...) EXPAND_VEC(k, __VA_ARGS__)
#define CONF_ENUM(k, d, ...) conf->k = d;
  CONF_FILE_FMT;
#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC
#undef CONF_ENUM
  char buf[MAX_CONF_LINE];
  while (get_conf_line_mpi(buf, f, rank)) {
    char *p = buf;
    while (*p && isspace(*p))
      p++;
    char *key = p;
    if (!*key || *key == '#')
      continue;

    char *last = p;
    while (*p && *p != '=') {
      if (!isspace(*p))
        last = p;
      p++;
    }
    *(last + 1) = 0;
    if (*p == '=')
      p++;
    while (*p && isspace(*p))
      p++;
    char *value = p;
    char *vlast = p;
    while (*p) {
      if (!isspace(*p))
        vlast = p;
      p++;
    }
    *(vlast + 1) = 0;
#define CONF_STRING(k, d) \
  if (!strcmp(key, #k))   \
    conf->k = strdup(value);
#define CONF_REAL(k, d) \
  if (!strcmp(key, #k)) \
    sscanf(value, "%lf", &conf->k);
#define CONF_INT(k, d)  \
  if (!strcmp(key, #k)) \
    sscanf(value, "%d", &conf->k);
#define CONF_REALBOX(k, ...) \
  if (!strcmp(key, #k))      \
    sscanf(value, "%lf%lf%lf%lf%lf%lf", &conf->k.lo.x, &conf->k.lo.y, &conf->k.lo.z, &conf->k.hi.x, &conf->k.hi.y, &conf->k.hi.z);
#define CONF_INTVEC(k, ...) \
  if (!strcmp(key, #k))     \
    sscanf(value, "%d%d%d", &conf->k.x, &conf->k.y, &conf->k.z);
#define CONF_ENUM(k, ...) \
  if (!strcmp(key, #k))   \
    conf->k = parse_##k(value);
    CONF_FILE_FMT;
#undef CONF_STRING
#undef CONF_REAL
#undef CONF_INT
#undef CONF_REALBOX
#undef CONF_INTVEC
#undef CONF_ENUM
  }
  if (conf->units == UNITS_REAL) {
    conf->ftm2v = 0.00041839999997254776;
    conf->mvv2e = 2390.0573615334906;
    conf->boltz = 0.0019872066999999998;
  }
}
#endif
